<template>
  <div class="formula-demo">
    <div class="formula-left-wrap">
      <div class="formula-fun-wrap">
        <div class="formula-fun-title">自定义函数</div>
        <div class="formula-fun-group" v-for="gp in funGroups">
          <p class="formula-fun-group__title" @click="gp.show = !gp.show">{{ gp.label }}</p>
          <div class="formula-fun-group__content" v-show="gp.show">
            <div class="formula-fun-item" v-for="item in gp.children" @click="handleInsertFun(item)">
              {{item.label}} ({{item.value}})
            </div>
          </div>
        </div>
      </div>
      <div class="formula-fun-wrap">
        <div class="formula-fun-title">变量</div>
        <div class="formula-fun-group" v-for="gp in variables" @click="handleInsertVariable(gp)">
          <p class="formula-fun-group__title" @click="gp.show = !gp.show">{{ gp.label }}</p>
          <div class="formula-fun-group__content" v-show="gp.show">
            <div class="formula-fun-item" v-for="item in gp.children" @click="handleInsertVariable(item)">
              {{item.label}} ({{item.value}})
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="formula-editor-wrap">
      <div class="editor-container" ref="editorContainerRef"></div>
      <p>全局变量JSON：</p>
      <textarea type="textarea" v-model="state.globalDataStr" placeholder="输入全局变量JSON" />
      <p><button @click="handleExec">执行表达式</button></p>
      <p>执行结果：</p>
      <textarea type="textarea" v-model="state.output" readonly />
    </div>
  </div>
</template>

<script lang="ts">
import {defineComponent, onMounted, reactive, ref, shallowRef} from 'vue';
import {FormulaEngine} from "@formula/engine";
import {createExtensions, createVariablePlugin, FormulaEditor} from "@formula/editor";

export default defineComponent({
  name: "FormulaDemo",
  setup() {
    const editorContainerRef = shallowRef<HTMLDivElement>();

    const funGroups = ref([
      {
        label: '日期函数', show: true,
        children: [
          {label: '转日期', value: 'DATE'},
          {label: '日期计算', value: 'DATEDELTA'},
          {label: '计算星期几', value: 'ISOWEEKNUM'},
          {label: '转时间戳', value: 'TIMESTAMP'},
        ]
      },
      {
        label: '逻辑函数', show: true,
        children: [
          {label: '且', value: 'AND'},
          {label: '判断函数', value: 'IF'},
          {label: '非', value: 'NOT'},
          {label: '或', value: 'OR'},
          {label: '异或', value: 'XOR'},
          {label: 'TRUE', value: 'TRUE'},
          {label: 'FALSE', value: 'FALSE'},
        ]
      },
      {
        label: '数学函数', show: true,
        children: [
          {label: '累加', value: 'SUM'},
          {label: '加', value: 'ADD'},
          {label: '减', value: 'SUBTRACT'},
          {label: '乘', value: 'MULTIPLY'},
          {label: '除', value: 'DIVIDE'},
          {label: '绝对值', value: 'ABS'},
          {label: '平均值', value: 'AVERAGE'},
          {label: '最大值', value: 'MAX'},
          {label: '最小值', value: 'MIN'},
        ]
      },
      {
        label: '文本函数', show: true,
        children: [
          {label: '连接', value: 'JOIN'},
          {label: '分割', value: 'SPLIT'},
          {label: '合并', value: 'CONCATENATE'},
          {label: '替换', value: 'REPLACE'},
          {label: '是否为空', value: 'ISEMPTY'},
          {label: '转小写', value: 'LOWER'},
          {label: '转大写', value: 'UPPER'},
        ]
      }
    ]);

    const variables = ref([
      {label: '数量', value: 'count', type: 'number', category: 'variable'},
      {label: '用户', value: 'user', type: 'object', category: 'variable',
        children: [
          {label: '名称', value: 'user.name', type: 'string', category: 'field'},
          {label: '年龄', value: 'user.age', type: 'number', category: 'field'},
          {label: '组织', value: 'user.org', type: 'object', category: 'variable',
          children: [
            {label: '名称', value: 'user.org.name', type: 'text', category: 'field'},
            {label: '编码', value: 'user.org.code', type: 'text', category: 'field'},
          ]
          },
        ]
      },
    ]);

    const tree2Array = <T = any>(treeData: T[] = []): T[] => {
      const arr: T[] = [];
      treeData.forEach(i => {
        arr.push({...i, children: undefined});
        if (i.children && i.children.length) {
          arr.push(...tree2Array(i.children));
        }
      });
      return arr;
    }

    const variableExtension = createVariablePlugin(
        [
          {label: '用户', value: 'user', type: 'object', category: 'field'},
          {label: '用户.名称', value: 'user.name', type: 'string', category: 'field'},
          {label: '用户.年龄', value: 'user.age', type: 'number', category: 'field'},
          {label: '用户.组织', value: 'user.org', type: 'number', category: 'field'},
          {label: '用户.组织.名称', value: 'user.org.name', type: 'text', category: 'field'},
        ],
        /{{(.+?)\}\}/g
    )


    const instance = new FormulaEditor(editorContainerRef, {
      extensions: [...createExtensions({
        keywords: [],
        functions: funGroups.value.map(i => i.children.map(c => c.value)).flat(),
        variables: tree2Array(variables.value),
        hints: [
          {label: '用户', value: '{user}', type: 'variable', detail: '用户',
            children: [
              {label: '名称', value: '{user.name}', type: 'text', detail: '用户的名称'},
              {label: '年龄', value: '{user.age}', type: 'variable', detail: '用户的年龄, number类型'},
              {label: '组织', value: '{user.org}', type: 'class', detail: '用户的组件',
                children: [
                  {label: '名称', value: '{user.org.name}', type: 'text', detail: '组件名称'},
                ]
              },
            ]
          },
        ]
      }), variableExtension]
    });

    const handleInsertFun = (fun) => {
      instance.insertValue(`${fun.value}($a)`, true);
    };
    const handleInsertVariable = (variable) => {
      if (!variable.children) {
        instance.insertValue(`{${variable.value}}`, true);
      }
    };

    const state = reactive({
      globalDataStr: '{"count": 15, "user": {"age": 18}}',
      output: ''
    });

    const formulaEngine = new FormulaEngine({
      functions: {
        'GETUSERNAME': function(users) {
          if(!users || !Array.isArray(users)) return ''
          return users.map(user=> user.name).join(',')
        },
      },
      scopeData: {},
    });

    const handleExec = () => {
      let globalData
      try {
        if (state.globalDataStr) {
          globalData = JSON.parse(state.globalDataStr)
        }
      } catch (error) {
        return alert('data value with:' + error)
      }
      console.error("globalData==>", globalData)
     const res = formulaEngine.exec(instance.getValue(), globalData);
      console.error('执行结果===>', res)
      state.output = res?.toString() ?? '';
    }

    onMounted(() => {
      setTimeout(() => {
        instance.setValue('SUM({user.age},{count}, IF({user.age} > 18,SUM(7,8),2),{count})');
      }, 300)
    })

    return {
      editorContainerRef,
      state,
      funGroups,
      variables,
      handleInsertFun,
      handleInsertVariable,
      handleExec
    }
  }
})
</script>

<style scoped>
.formula-demo {
  width: 100%;
  height: 100%;
  padding: 10px 20px;
  background-color: #ffffff;
  display: flex;
}
.formula-left-wrap {
  width: 220px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
}
.formula-fun-wrap {
  flex: 1;
  overflow-y: auto;
}
.formula-fun-title {
  height: 32px;
  display: flex;
  align-items: center;
  background-color: #9499ff;
}

.formula-fun-group .formula-fun-group__title {
  cursor: pointer;
  color: #333333;
}
.formula-fun-group .formula-fun-group__content {
}
.formula-fun-group .formula-fun-item {
  cursor: pointer;
  line-height: 20px;
  padding-left: 16px;
  color: #646cff;
}
.formula-fun-group .formula-fun-item:hover {
  background-color: #f5f5f5;
}

.formula-editor-wrap {
  display: flex;
  flex-direction: column;
}
.editor-container {
  width: 600px;
  height: 300px;
  flex-shrink: 0;
}
</style>
